//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

///////////////////////////////////////////////////////////////////////////////
//
// apiMeshCreator.cpp
//
////////////////////////////////////////////////////////////////////////////////

#include <math.h>
#include <maya/MIOStream.h>

#include <apiMeshCreator.h>           
#include <apiMeshData.h>
#include <api_macros.h>

#include <maya/MFnMesh.h>
#include <maya/MFnPluginData.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnEnumAttribute.h>
#include <maya/MFnNumericAttribute.h>

#ifndef M_PI
#define M_PI		3.14159265358979323846
#endif

#ifndef M_PI_2
#define M_PI_2		1.57079632679489661923
#endif

////////////////////////////////////////////////////////////////////////////////
//
// Shape implementation
//
////////////////////////////////////////////////////////////////////////////////

MObject apiMeshCreator::size;
MObject apiMeshCreator::shapeType;
MObject apiMeshCreator::inputMesh;
MObject apiMeshCreator::outputSurface;

MTypeId apiMeshCreator::id( 0x80089 );

apiMeshCreator::apiMeshCreator() {}
apiMeshCreator::~apiMeshCreator() {}

///////////////////////////////////////////////////////////////////////////////
//
// Overrides
//
///////////////////////////////////////////////////////////////////////////////

/* override */
MStatus apiMeshCreator::compute( const MPlug& plug, MDataBlock& datablock )
//
// Description
//
//    When input attributes are dirty this method will be called to
//    recompute the output attributes.
//
{ 
    MStatus stat;
    if ( plug == outputSurface ) {
		// Create some user defined geometry data and access the
		// geometry so we can set it
		//
		MFnPluginData fnDataCreator;
		MTypeId tmpid( apiMeshData::id );
		
		fnDataCreator.create( tmpid, &stat );
		MCHECKERROR( stat, "compute : error creating apiMeshData")

		apiMeshData * newData = (apiMeshData*)fnDataCreator.data( &stat );
		MCHECKERROR( stat, "compute : error gettin at proxy apiMeshData object")

		apiMeshGeom * geomPtr = newData->fGeometry;

		// If there is an input mesh then copy it's values
		// and construct some apiMeshGeom for it.
		//
		bool hasHistory = computeInputMesh( plug, datablock,
											geomPtr->vertices,
											geomPtr->face_counts,
											geomPtr->face_connects,
											geomPtr->normals );
											
		// There is no input mesh so check the shapeType attribute
		// and create either a cube or a sphere.
		//
		if ( !hasHistory ) {
			MDataHandle sizeHandle = datablock.inputValue( size );
			double shape_size = sizeHandle.asDouble();
			MDataHandle typeHandle = datablock.inputValue( shapeType );
			short shape_type = typeHandle.asShort();

			switch( shape_type )
			{
				case 0 : // build a cube
					buildCube( shape_size,
							   geomPtr->vertices,
							   geomPtr->face_counts,
							   geomPtr->face_connects,
							   geomPtr->normals );
					break;
			
				case 1 : // buld a sphere
					buildSphere( shape_size,
								 32,
								 geomPtr->vertices,
								 geomPtr->face_counts,
								 geomPtr->face_connects,
								 geomPtr->normals );
					break;
			} // end switch
		}

		geomPtr->faceCount = geomPtr->face_counts.length();

		// Assign the new data to the outputSurface handle
		//
		MDataHandle outHandle = datablock.outputValue( outputSurface );
		outHandle.set( newData );
		datablock.setClean( plug );
		return MS::kSuccess;
    }
    else {
        return MS::kUnknownParameter;
    }
}

///////////////////////////////////////////////////////////////////////////////
//
// Helper routines
//
///////////////////////////////////////////////////////////////////////////////

void apiMeshCreator::buildCube( double cube_size, MPointArray& pa,
								MIntArray& faceCounts, MIntArray& faceConnects,
								MVectorArray& normals )
//
// Description
//
//    Constructs a cube
//
{
	const int num_faces			= 6;
	const int num_face_connects	= 24;
	const double normal_value = 0.5775;

	pa.clear(); faceCounts.clear(); faceConnects.clear();

	pa.append( MPoint( -cube_size, -cube_size, -cube_size ) );
	pa.append( MPoint(  cube_size, -cube_size, -cube_size ) );
	pa.append( MPoint(  cube_size, -cube_size,  cube_size ) );
	pa.append( MPoint( -cube_size, -cube_size,  cube_size ) );
	pa.append( MPoint( -cube_size,  cube_size, -cube_size ) );
	pa.append( MPoint( -cube_size,  cube_size,  cube_size ) );
	pa.append( MPoint(  cube_size,  cube_size,  cube_size ) );
	pa.append( MPoint(  cube_size,  cube_size, -cube_size ) );

	normals.append( MVector( -normal_value, -normal_value, -normal_value ) );
	normals.append( MVector(  normal_value, -normal_value, -normal_value ) );
	normals.append( MVector(  normal_value, -normal_value,  normal_value ) );
	normals.append( MVector( -normal_value, -normal_value,  normal_value ) );
	normals.append( MVector( -normal_value,  normal_value, -normal_value ) );
	normals.append( MVector( -normal_value,  normal_value,  normal_value ) );
	normals.append( MVector(  normal_value,  normal_value,  normal_value ) );
	normals.append( MVector(  normal_value,  normal_value, -normal_value ) );

	// Set up an array containing the number of vertices
	// for each of the 6 cube faces (4 verticies per face)
	//
	int face_counts[num_faces] = { 4, 4, 4, 4, 4, 4 };
	int i;
	for ( i=0; i<num_faces; i++ )
	{
		faceCounts.append( face_counts[i] );
	}

	// Set up and array to assign vertices from pa to each face 
	//
	int face_connects[ num_face_connects ] = {	0, 1, 2, 3,
												4, 5, 6, 7,
												3, 2, 6, 5,
												0, 3, 5, 4,
												0, 4, 7, 1,
												1, 7, 6, 2	};
	for ( i=0; i<num_face_connects; i++ )
	{
		faceConnects.append( face_connects[i] );
	}
}

void apiMeshCreator::buildSphere( double rad, int div, MPointArray & vertices,
								 MIntArray & counts, MIntArray & connects,
								 MVectorArray & normals )
//
// Description
//
//    Create circles of vertices starting with 
//    the top pole ending with the botton pole
//
{
	double u = -M_PI_2;
	double v = -M_PI;
	double u_delta = M_PI / ((double)div); 
	double v_delta = 2 * M_PI / ((double)div); 

	MPoint topPole( 0.0, rad, 0.0 );
	MPoint botPole( 0.0, -rad, 0.0 );

	// Build the vertex and normal table
	//
	vertices.append( botPole );
	normals.append( botPole-MPoint::origin );
	int i;
	for ( i=0; i<(div-1); i++ )
	{
		u += u_delta;
		v = -M_PI;

		for ( int j=0; j<div; j++ )
		{
			double x = rad * cos(u) * cos(v);
			double y = rad * sin(u);
			double z = rad * cos(u) * sin(v) ;
			MPoint pnt( x, y, z );
			vertices.append( pnt );
			normals.append( pnt-MPoint::origin );
			v += v_delta;
		}
	}
	vertices.append( topPole );
	normals.append( topPole-MPoint::origin );

	// Create the connectivity lists
	//
	int vid = 1;
	int numV = 0;
	for ( i=0; i<div; i++ )
	{
		for ( int j=0; j<div; j++ )
		{
			if ( i==0 ) {
				counts.append( 3 );
				connects.append( 0 );
				connects.append( j+vid );
				connects.append( (j==(div-1)) ? vid : j+vid+1 );
			}
			else if ( i==(div-1) ) {
				counts.append( 3 );
				connects.append( j+vid+1-div );
				connects.append( vid+1 );
				connects.append( j==(div-1) ? vid+1-div : j+vid+2-div );
			}
			else {
				counts.append( 4 );
				connects.append( j + vid+1-div );
				connects.append( j + vid+1 );
				connects.append( j == (div-1) ? vid+1 : j+vid+2 );
				connects.append( j == (div-1) ? vid+1-div : j+vid+2-div );
			}
			numV++;
		}
		vid = numV;
	}
}

MStatus apiMeshCreator::computeInputMesh( const MPlug& plug,
										  MDataBlock& datablock,
										  MPointArray& vertices,
										  MIntArray& counts,
										  MIntArray& connects,
										  MVectorArray& normals )
//
// Description
//
//     This function takes an input surface of type kMeshData and converts
//     the geometry into this nodes attributes.
//     Returns kFailure if nothing is connected.
//
{
	MStatus stat;

    // Get the input subdiv
    //        
    MDataHandle inputData = datablock.inputValue( inputMesh, &stat );
    MCHECKERROR( stat, "compute get inputMesh")
    MObject surf = inputData.asMesh();

	// Check if anything is connected
	//
	MObject thisObj = thisMObject();
	MPlug surfPlug( thisObj, inputMesh );
	if ( !surfPlug.isConnected() ) {
    	stat = datablock.setClean( plug );
	    MCHECKERROR( stat, "compute setClean" )
		return MS::kFailure;
	}

	// Extract the mesh data
	//
   	MFnMesh surfFn (surf, &stat);
	MCHECKERROR( stat, "compute - MFnMesh error" )
	stat = surfFn.getPoints( vertices, MSpace::kObject );
	MCHECKERROR( stat, "compute getPoints")
	
	for ( int i=0; i<surfFn.numPolygons(); i++ )
	{
		MIntArray polyVerts;
		surfFn.getPolygonVertices( i, polyVerts );
		int pvc = polyVerts.length();
		counts.append( pvc );
		for ( int v=0; v<pvc; v++ ) {
			connects.append( polyVerts[v] );
		}
	}

	for ( int n=0; n<(int)vertices.length(); n++ )
	{
		MVector normal;
		surfFn.getVertexNormal( n, normal );
		normals.append( normal );
	}

	return MS::kSuccess;
}

void* apiMeshCreator::creator()
//
// Description
//    Called internally to create a new instance of the users MPx node.
//
{
	return new apiMeshCreator();
}

MStatus apiMeshCreator::initialize()
//
// Description
//
//    Attribute (static) initialization. See api_macros.h.
//
{ 
	MStatus				stat;
    MFnTypedAttribute	typedAttr;
	MFnEnumAttribute	enumAttr;

	// ----------------------- INPUTS -------------------------

	MAKE_NUMERIC_ATTR(	size, "size", "sz",
						MFnNumericData::kDouble, 1,
						false, false, true );

	shapeType = enumAttr.create( "shapeType", "st", 0, &stat );
	MCHECKERROR( stat, "create shapeType attribute" );
	stat = enumAttr.addField( "cube", 0 );
	MCHECKERROR( stat, "add enum type cube" );
	stat = enumAttr.addField( "sphere", 1 );
	MCHECKERROR( stat, "add enum type sphere" );
    CHECK_MSTATUS( enumAttr.setHidden( false ) );
    CHECK_MSTATUS( enumAttr.setKeyable( true ) );
	ADD_ATTRIBUTE( shapeType );
	
	MAKE_TYPED_ATTR( inputMesh, "inputMesh", "im", MFnData::kMesh, NULL );

	// ----------------------- OUTPUTS -------------------------
    outputSurface = typedAttr.create( "outputSurface", "os",
									  apiMeshData::id,
									  MObject::kNullObj, &stat );
    MCHECKERROR( stat, "create outputSurface attribute" )
	typedAttr.setWritable( false );
    ADD_ATTRIBUTE( outputSurface );

	// ---------- Specify what inputs affect the outputs ----------
	//
    ATTRIBUTE_AFFECTS( inputMesh, outputSurface );
    ATTRIBUTE_AFFECTS( size, outputSurface );
    ATTRIBUTE_AFFECTS( shapeType, outputSurface );

	return MS::kSuccess;
}
